package gu.sql2java.store;

import static gu.sql2java.store.BinaryUtils.*;
import static gu.sql2java.store.URLInfo.wrap;

import java.io.IOException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import ref.org.apache.commons.jnet.Installer;

/**
 * 基于{@link URLStreamHandler}实现二进制存储接口{@link URLStore}的抽象类<br>
 * 实现{@link URLStreamHandlerFactory}接口,应用启动时应调用{@link #intall()}将当前实例安装到JVM
 * @author guyadong
 *
 */
public abstract class BaseURLStore implements URLStreamHandlerFactory, URLStore {

	private volatile boolean installed = false;
	private final Map<String, Class<?>> optionalParamTypes = new Hashtable<>();
	protected final ThreadLocal<Map<String, Object>> additionalParams = new ThreadLocal<>();
	protected BaseURLStore() {
		this(null);
	}
	
	protected BaseURLStore(Map<String, Class<?>> optionalParamTypes) {
		super();
		if(optionalParamTypes == null){
			optionalParamTypes = Collections.emptyMap();
		}
		this.optionalParamTypes.putAll(optionalParamTypes);
	}

	/**
	 * 存储图像数据
	 * @param binary 二进制数据字节数组
	 * @param md5 imageBytes的MD5校验码
	 * @param extension 文件后缀,可为{@code null} 
	 * @param overwrite 文件存在时是否重写
	 * @param makeURLOnly 为{@code true}时不存储数据只返回存储URL
	 * @return 存储URL
	 * @throws IOException
	 */
	protected abstract URL doStore(byte[] binary,String md5, String extension, boolean overwrite, boolean makeURLOnly) throws IOException;

	/**
	 * 判断存储 URl 是否存在 
	 * @param storedURL 存储URL
	 */
	protected abstract boolean doExists(URL storedURL);
	
	/**
	 * 查找指定MD5的二进制数据
	 * @param md5 MD5校验码
	 * @return 数据存储URL,找不到返回{@code null}
	 */
	protected abstract URL doFind(String md5);
	/**
	 * 指定指定的二进制数据
	 * @param storedURL 存储的URL
	 * @return 删除成功返回{@code true},否则返回{@code false}
	 * @throws IOException
	 */
	protected abstract boolean doDelete(URL storedURL) throws IOException;

	protected abstract URLStreamHandler doGetURLStreamHandler();
	
	protected final URL find(URLInfo binary){
		if(isStored(binary.location)){
			return doExists(binary.location) ? binary.location : null;
		}
		String md5 = binary.getMD5();
		return md5 == null ? null : doFind(md5);
	}

	@Override
	public final boolean isStored(URL url){
		return null != url && url.getProtocol().equals(getProtocol());
	}
	
	@Override
	public final boolean exists(URL url){
		return null != find(wrap(url));
	}
	
	@Override
	public final <T>URL store(T input,String md5,String extension, boolean overwrite, boolean makeURLOnly) throws IOException {
		byte[] binary;
		if(makeURLOnly){
			binary = getBytes(input);
			if(!validMd5(md5)){
				throw new IOException("VALID md5 required while make URL only");
			}
		}else{
			binary = getBytesNotEmpty(input);
			if(!validMd5(md5)){
				md5 = getMD5String(binary);
			}
		}
		return doStore(binary,md5,extension, overwrite, makeURLOnly);
	}

	@Override
	public final boolean delete(String md5) throws IOException{
		if(validMd5(md5)){
			URL storedURL;
			if(null != (storedURL = doFind(md5))){
				return doDelete(storedURL);	
			}
		}
		return false;
	}

	@Override
	public final URL store(URL url, boolean overwrite, boolean makeURLOnly) throws IOException {
		URLInfo data = wrap(url);
		URL storedURL;
		if(null == (storedURL = find(data)) || overwrite){
			storedURL = doStore(
					getBytesNotEmpty(data.location), 
					data.getMD5(), 
					data.extension, 
					overwrite, 
					makeURLOnly);
		}
		return storedURL;
	}

	@Override
	public final boolean delete(URL url) throws IOException{
		URL storedURL = url;
		if(!isStored(storedURL)){
			if(null == (storedURL = find(wrap(url)))){
				return false;
			}
		}
		return doDelete(storedURL);
	}

	@Override
	public final BaseURLStore setAdditionalParam(String name,Object value){
		if(null != name){
			Class<?> type = optionalParamTypes.get(name);
			if(null != type && null != value){
				if(!type.isInstance(value)){
					throw new IllegalArgumentException(String.format("INVALID  param type for %s , %srequired",name,type));
				}
				if(additionalParams.get() == null){
					additionalParams.set(new HashMap<String,Object>());
				}
				additionalParams.get().put(name, value);
			}
		}
		return this;
	}
	@Override
	public final URLStreamHandler createURLStreamHandler(String protocol) {
        if(getProtocol().equals(protocol)){
            return doGetURLStreamHandler();
        }
        return null;
	}

	@Override
	public final BaseURLStore intall() throws Exception{
		// double check
		if(!installed){
			synchronized(this){
				if(!installed){
					Installer.setURLStreamHandlerFactory(this);
					installed = true;
				}
			}
		}
		return this;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((getProtocol() == null) ? 0 : getProtocol().hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof BaseURLStore))
			return false;
		BaseURLStore other = (BaseURLStore) obj;
		if (getProtocol() == null) {
			if (other.getProtocol() != null)
				return false;
		} else if (!getProtocol().equals(other.getProtocol()))
			return false;
		return true;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("BaseURLStore [protocol=");
		builder.append(getProtocol());
		builder.append("]");
		return builder.toString();
	}
	
}


